iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0
Modern Web

了不起的 Svelte系列 第 22

第 22 天:Svelte 資料綁定 `bind`

  • 分享至 

  • xImage
  •  

第 22 天:Svelte 資料綁定 bind

「要不我們都用資料綁定如何?」Svelte 如此建議。他從那張又熱又厚的綠色皮革椅子當中起身。「我剛好把它停在樹陰下。」

~節錄自《The Great Svelte:第七章》

第 22 天要講的事

  1. Svelte 資料綁定 bind 語法
  2. 元件資料綁定
  3. <input> 元素資料綁定

  經過了這麼多天的介紹跟實作,相信我們對於 Svelte 當中資料的傳遞應該很熟悉了。就像在第 10 天:Svelte 元件的 Property討論過的,資料可以藉由元件 Property 的方式,從父元件 (parent component) 傳遞給子元件。同時,若是子元件 (child component) 需要改變由父元件傳遞過來的資料,則可以透過第 12 天:Svelte 的事件:事件傳遞介紹的方法,利用事件提醒父元件 (parent) 該改變資料內容了,再由父元件 (parent component) 直接修改資料。
  也就是說,在 Svelte 當中,父元件 (parent component) 跟子元件 (child component) 的溝通,是藉由 Property 將資料往下傳遞,並透過往上傳遞的事件來改變父元件 (parent) 當中的資料,如圖一。

https://ithelp.ithome.com.tw/upload/images/20231007/20120178tWQZkCGUkN.png
圖一、資料向下,事件向上

  因此在這樣的設計方針上,就會看到父元件中的資料以 Property 的方式 property={data} 傳遞給子元件,並且有一個事件處理器 on:changeData 接受來自子元件 (child component) 的提醒去做資料更新的動作:

<!-- 在父元件當中 -->
<ChildComponent property={data} on:changeData />

  而子元件當中則會看到相對應的該傳向父元件的事件產生器 dispatch('changeData', updatedProperty)

<!-- 在子元件當中 -->
<script>
  export let property
  const handleChange = () => dispatch('changeData', updatedProperty);
</script>

  這樣寫是沒有什麼問題,唯一的問題就是…太麻煩了。對一個有點規模的專案,資料傳遞是這麼的重要,而且發生頻率又是非常頻繁,如果對每個需要傳遞的資料都來上這麼套,撰寫程式碼想必會變得非常辛苦。因此 Svelte 提供了一個方便的語法來處理這個工作,也就是資料綁定 bind

Svelte 資料綁定 bind 語法

  資料綁定 bind 的任務很簡單,就是要打破資料只能由上而下單一方向改變這件事。藉由 bind,我們可以將父元件 (component) 當中的資料跟子元件 (child component) 的資料綁定在一起,不僅是父元件 (parent component) 的資料改變會影響子元件,還可以在子元件 (child component) 當中直接改變資料,同時將這個變化連動回到父元件 (parent component)。

<!-- 在父元件當中改用 bind -->
<ChildComponent bind:property={data} />
  • 第二行:<ChildComponent bind:property={data} />
      只要用 bind:property={data} 就能夠將父元件 (parent component) 的資料跟子元件 (child component) 的資料綁在一起了。data 是父元件 (parent component) 當中的資料,property 則是子元件 (child component) 的 Property,可以作為子元件 (child component) 自己的資料來使用。
<!-- 在子元件當中改用 bind -->
<script>
  export let property
  const handleChange = () => property = updatedProperty;
</script>
  • 第三行:const handleChange = () => property = updatedProperty;
      在子元件 (child component) 當中,我們省去了向父元件 (parent component) 傳遞事件的手續,直接將 property 重新賦值,就能直接改變父元件 (parent component) 當中透過 bind:property={data} 綁定在一起的 data 了。

  是不是很方便呢?就讓我們實際來試試看吧。

元件資料綁定

  還記得我們在色票產生器的專案當中,將所有色票的資料放在主要元件 App.svelte 的變數 palettes 當中,並作為 Property 傳向子元件 Palettes.svelte

/src/App.svelte
<!-- 省略一些無關的 HTML 元素 -->
<Palettes {palettes} on:changeLock={handleLock} />

  同時在子元件 Palettes.svelte 當中,我們接收來自主要元件 App.sveltepalettes 這個變數,並透過 dispatch("changeLock", id) 來讓主要元件 App.svelte 知道 palettes 該做出相對應的更新:

<script>
  /* 省略一些無關的程式碼 */
  import { createEventDispatcher } from "svelte";
  export let palettes;

  const dispatch = createEventDispatcher();
  const handleClick = (id) => dispatch("changeLock", id);
</script>

<!-- 省略一些無關的 HTML 元素 -->
<div class="lock-icon" on:click={() => handleClick(id)}>
  {#if locked}
    <img src={lock} alt="color-locked" />
  {:else}
    <img src={unlock} alt="color-unlocked" />
  {/if}
</div>

  然後再回到主要元件 App.svelte 實作出接收 changeLock 事件的事件處理器 handleLock

/src/App.svelte
<script>
  /* 省略一些無關的程式碼 */
  const handleLock = (e) => {
    console.log(e);
    const targetId = e.detail;
    palettes = palettes.map((palette) => {
      if (palette.id === targetId) {
        return { ...palette, locked: !palette.locked };
      } else {
        return palette;
      }
    });
  };
</script>

  經過一番折騰,我們的解鎖/上鎖功能終於做出來了。現在既然學會了 bind ,就讓我們看看如何省去這些折騰吧:

/src/App.svelte
<!-- 省略一些無關的 HTML 元素 -->
<Palettes bind:palettes={palettes} />
  • 第二行:<Palettes bind:palettes={palettes} />
      首先在 App.svelte 當中,用 bind:palettes={palettes} 將主要元件的變數 palettesPalettes.svelte 這個元件的 Property palettes 綁定在一起。還記的我們在第 10 天:Svelte 元件的 Property 當中提過,只要變數跟 Property 名稱相同,就可以簡寫。在使用 bind 的時候也不例外。所以我們現在這行程式碼,其實也可以寫成:
<Palettes bind:palettes />

  就是這麼簡單。那讓我們進到子元件 Palettes.svelte,來看看該做哪些修改:

/src/lib/Palettes.svelte
<script>
  /* 省略一些無關的程式碼 */
  // import { createEventDispatcher } from "svelte";
  export let palettes;

  // const dispatch = createEventDispatcher();
  // const handleClick = (id) => dispatch("changeLock", id);
</script>

<!-- 省略一些無關的 HTML 元素 -->
<div class="lock-icon" on:click={() => (locked = !locked)}>
  {#if locked}
    <img src={lock} alt="color-locked" />
  {:else}
    <img src={unlock} alt="color-unlocked" />
  {/if}
</div>
  • 第三行:// import { createEventDispatcher } from "svelte";
      使用了 bind,我們就不用向主要元件發送事件提醒修改資料了,所以 createEventDispatcher 就用不到了。

  • 第六行:// const dispatch = createEventDispatcher();
      這個用不到了。

  • 第七行:// const handleClick = (id) => dispatch("changeLock", id);
      這個也用不到了。

  • 第十一行:<div class="lock-icon" on:click={() => (locked = !locked)}>
      因為 bind 已經將主要元件跟子元件的 palettes 綁定在一起,現在我們直接在子元件當中修改資料,這個改動就會連動到主要元件。所以簡簡單單的說 on:click={() => (locked = !locked)} 就可以囉!

  回到主要元件 App.svelte,有個事件處理器 handleLock 再也用不到了:

/src/App.svelte
<script>
  /* 省略一些無關的程式碼 */
  // const handleLock = (e) => {
  //   console.log(e);
  //   const targetId = e.detail;
  //   palettes = palettes.map((palette) => {
  //     if (palette.id === targetId) {
  //       return { ...palette, locked: !palette.locked };
  //     } else {
  //       return palette;
  //     }
  //   });
  // };

/* 稍微修改一下隨機產生色票的程式碼 */
  onMount(() => {
    const intervalId = setInterval(() => {
      palettes = palettes.map((palette) => {
        if (palette.locked) return palette;
        else
          return {
            ...palette,
            hex: generateHex(),
          };
      });
    }, 2000);

    return () => clearInterval(intervalId);
  });
</script>
  • 第三行:// const handleLock = (e) => {
      辛辛苦苦作出來的 handleLock 可以功成身退了。

  • 第十八行:palettes = palettes.map((palette) => {
      跟 bind 主題無關,稍微修改一下第 21 天:Svelte 的生命週期函式:onMount做出來,隨機產生色票的程式碼。

  • 第十九行:if (palette.locked) return palette;
      如果 palette.locked,也就是色票處於上鎖的狀態,那就不要改變這張色票的顏色。否則的話,就隨機產生新的顏色。

https://i.ibb.co/3RwTrSb/22.gif
圖二、輕輕鬆鬆讓色票解鎖/上鎖

  學會 bind 之後,是不是覺得 Svelte 程式碼越看越親切了呢!那麼今天的內容就到這裡了,謝謝大家。


上一篇
第 21 天:Svelte 的生命週期函式:`onMount`
下一篇
第 23 天:Svelte 資料綁定 `bind`(二)
系列文
了不起的 Svelte30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言